#pragma once
//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------

namespace sqb
{
namespace internal
{
//----------------------------------------------------------------------------------------------------------------------
// MatchHelper
//----------------------------------------------------------------------------------------------------------------------
template<typename ... ParameterTypes>
struct MatchHelper;

//----------------------------------------------------------------------------------------------------------------------
template< typename ParameterType, typename ... RemainingParameterTypes >
struct MatchHelper<ParameterType, RemainingParameterTypes ...>
{
  // recursive call calls sqb::Match for one parameter then calls MatchAll for the remaining parameters
  static inline SQRESULT MatchAll(HSQUIRRELVM vm, SQInteger index)
  {
    if (!sqb::Match(sqb::TypeWrapper<ParameterType>(), vm, index))
    {
      sqb::StackHandler sh(vm);
      return sh.ThrowParamError(index, sqb::TypeInfo<ParameterType>().m_typeName);
    }

    return MatchHelper<RemainingParameterTypes ...>::MatchAll(vm, index + 1);
  }
};

//----------------------------------------------------------------------------------------------------------------------
template<>
struct MatchHelper<>
{
  // terminating case ends the recursion
  static inline SQRESULT MatchAll(HSQUIRRELVM SQBIND_UNUSED(vm), SQInteger SQBIND_UNUSED(index))
  {
    return SQ_OK;
  }
};


} // namespace internal

#if defined(SQBIND_COMPILER_MSVC)
#pragma warning(push)
// For bound functions with no parameters disable warning:
// C4189: 'lastIndex' : local variable is initialized but not referenced
#pragma warning(disable: 4189)
#endif

//----------------------------------------------------------------------------------------------------------------------
// ReturnSpecialisation<ReturnType>
//----------------------------------------------------------------------------------------------------------------------
template<typename ReturnType>
template<typename ... ParameterTypes>
inline SQRESULT ReturnSpecialisation<ReturnType>::Call(
  ReturnType (*function)(ParameterTypes ...),
  HSQUIRRELVM vm,
  SQInteger index)
{
  const auto matchAllResult = internal::MatchHelper<ParameterTypes ...>::MatchAll(vm, index);
  if (SQ_FAILED(matchAllResult))
  {
    return matchAllResult;
  }

  auto lastIndex = index + (sizeof ... (ParameterTypes) - 1);
  auto result = function(sqb::Get(sqb::TypeWrapper<ParameterTypes>(), vm, lastIndex--) ...);

  return sqb::Push(vm, result);
}

//----------------------------------------------------------------------------------------------------------------------
template<>
template<typename ... ParameterTypes>
inline SQRESULT ReturnSpecialisation<void>::Call(void (*function)(ParameterTypes ...), HSQUIRRELVM vm, SQInteger index)
{
  const auto matchAllResult = internal::MatchHelper<ParameterTypes ...>::MatchAll(vm, index);
  if (SQ_FAILED(matchAllResult))
  {
    return matchAllResult;
  }

  auto lastIndex = index + (sizeof ... (ParameterTypes) - 1);
  function(sqb::Get(sqb::TypeWrapper<ParameterTypes>(), vm, lastIndex--) ...);

  return 0;
}

//----------------------------------------------------------------------------------------------------------------------
template<typename ReturnType>
template<typename InstanceType, typename ... ParameterTypes>
inline SQRESULT ReturnSpecialisation<ReturnType>::Call(
  InstanceType &instance,
  ReturnType (InstanceType::*function)(ParameterTypes ...),
  HSQUIRRELVM vm,
  SQInteger index)
{
  const auto matchAllResult = internal::MatchHelper<ParameterTypes ...>::MatchAll(vm, index);
  if (SQ_FAILED(matchAllResult))
  {
    return matchAllResult;
  }

  auto lastIndex = index + (sizeof ... (ParameterTypes) - 1);
  auto result = (instance.*function)(sqb::Get(sqb::TypeWrapper<ParameterTypes>(), vm, lastIndex--) ...);

  return sqb::Push(vm, result);
}

//----------------------------------------------------------------------------------------------------------------------
template<>
template<typename InstanceType, typename ... ParameterTypes>
inline SQRESULT ReturnSpecialisation<void>::Call(
  InstanceType &instance,
  void (InstanceType::*function)(ParameterTypes ...),
  HSQUIRRELVM vm,
  SQInteger index)
{
  const auto matchAllResult = internal::MatchHelper<ParameterTypes ...>::MatchAll(vm, index);
  if (SQ_FAILED(matchAllResult))
  {
    return matchAllResult;
  }

  auto lastIndex = index + (sizeof ... (ParameterTypes) - 1);
  (instance.*function)(sqb::Get(sqb::TypeWrapper<ParameterTypes>(), vm, lastIndex--) ...);

  return 0;
}

//----------------------------------------------------------------------------------------------------------------------
template<typename ReturnType>
template<typename InstanceType, typename ... ParameterTypes>
inline SQRESULT ReturnSpecialisation<ReturnType>::Call(
  const InstanceType &instance,
  ReturnType (InstanceType::*function)(ParameterTypes ...) const,
  HSQUIRRELVM vm,
  SQInteger index)
{
  const auto matchAllResult = internal::MatchHelper<ParameterTypes ...>::MatchAll(vm, index);
  if (SQ_FAILED(matchAllResult))
  {
    return matchAllResult;
  }

  auto lastIndex = index + (sizeof ... (ParameterTypes) - 1);
  auto result = (instance.*function)(sqb::Get(sqb::TypeWrapper<ParameterTypes>(), vm, lastIndex--) ...);

  return sqb::Push(vm, result);
}

//----------------------------------------------------------------------------------------------------------------------
template<>
template<typename InstanceType, typename ... ParameterTypes>
inline SQRESULT ReturnSpecialisation<void>::Call(
  const InstanceType &instance,
  void (InstanceType::*function)(ParameterTypes ...) const,
  HSQUIRRELVM vm,
  SQInteger index)
{
  const auto matchAllResult = internal::MatchHelper<ParameterTypes ...>::MatchAll(vm, index);
  if (SQ_FAILED(matchAllResult))
  {
    return matchAllResult;
  }

  auto lastIndex = index + (sizeof ... (ParameterTypes) - 1);
  (instance.*function)(sqb::Get(sqb::TypeWrapper<ParameterTypes>(), vm, lastIndex--) ...);

  return 0;
}

#if defined(SQBIND_COMPILER_MSVC)
#pragma warning(pop)
#endif

} // namespace sqb
